﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using Ignitron.EEngine.Adapters.DX.System;
using SharpDX;
using SharpDX.Direct3D11;

namespace Ignitron.EEngine.Adapters.DX.Graphics
{
    public class Graphics : ICloneable
    {
        /// <summary>
        /// Class for rendering the basic primitives
        /// </summary>
        private PrimitivesRenderer mPrimitivesRenderer;

        /// <summary>
        /// The texture shader used for rendering without lighting
        /// </summary>
        private TextureShader mTextureShader;

        /// <summary>
        /// The lighting shader used for rendering with lighting
        /// </summary>
        private LightingShader mLightingShader;

        
        public Graphics()
        {
        }

        /// <summary>
        /// Returns the directX device
        /// </summary>
        public Device DirectXDevice
        {
            get { return D3D.Device; }
        }

        /// <summary>
        /// Returns the D3D 11 Devices
        /// </summary>
        internal DX11 DirectX11
        {
            get { return D3D; }
        }

        /// <summary>
        /// Property getter for texture shader object
        /// </summary>
        internal TextureShader TextureShader
        {
            get { return mTextureShader; }
        }

        /// <summary>
        /// Property getter for lighting shader
        /// </summary>
        internal LightingShader LightingShader
        {
            get { return mLightingShader; }
        }

        /// <summary>
        /// Returns the primitives renderer
        /// </summary>
        internal PrimitivesRenderer PrimitivesRenderer
        {
            get { return mPrimitivesRenderer;  }
        }

        #region Variables / Properties
        private DX11 D3D { get; set; }

        // DEBUG
        internal Camera mCamera;

        internal LightingManager LightingManager
        { get; private set; }

        internal Matrix WorldMatrix
        { get; set; }
        //private Model Model { get; set; }
        //private LightShader LightShader { get; set; }
        //private Light Light { get; set; }
        #endregion

        #region Static Variables
        static float Rotation { get; set; }
        #endregion

        #region Methods
        public bool Initialize(SystemConfiguration configuration, IntPtr windowHandle)
        {
            try
            {
                // Create the Direct3D object.
                D3D = new DX11();
                // Initialize the Direct3D object.
                if (!D3D.Initialize(configuration, windowHandle))
                    return false;

                // try to initialize the primitives renderer
                mPrimitivesRenderer = new PrimitivesRenderer();
                if (!mPrimitivesRenderer.Initialize(DirectXDevice, windowHandle))
                    return false;

                // Create the camera object
                mCamera = new Camera();
                mCamera.SetPosition(0, 0, -4);

                // create the lighting manager
                LightingManager = new LightingManager();

                // initialize shaders

                // initialize texture shader
                mTextureShader = new TextureShader();
                if (mTextureShader.Initialize(D3D.Device, windowHandle, "Texture.vs", "Texture.ps") == false)
                {
                    throw new ApplicationException();
                }

                // Initialize lighting shader
                mLightingShader = new LightingShader();
                if (mLightingShader.Initialize(D3D.Device, windowHandle, "Light.vs", "Light.ps") == false)
                {
                    throw new ApplicationException();
                }

                return true;
            }
            catch (Exception)
            {
                //MessageBox.Show("Could not initialize Direct3D\nError is '" + ex.Message + "'");
                return false;
            }
        }

        public void Shutdown()
        {
            // Release the primitives renderer
            if (mPrimitivesRenderer != null)
            {
                mPrimitivesRenderer.Shutdown();
                mPrimitivesRenderer = null;
            }

            // Release the shader objects.
            if (mLightingShader != null)
            {
                mLightingShader.Shuddown();
                mLightingShader = null;
            }

            if (mTextureShader != null)
            {
                mTextureShader.Shuddown();
                mTextureShader = null;
            }

            // Release the camera object.
            if (mCamera != null)
            {
                mCamera = null;
            }

            // Release the Direct3D object.
            if (D3D != null)
            {
                D3D.Shutdown();
                D3D = null;
            }
        }

        //public bool Frame()
        //{
        //    // Update the rotation variables each frame.
        //    Rotate();

        //    // Render the graphics scene.
        //    return Render(Rotation);
        //}

        #region Public API

        public void Resize(int width, int height)
        {
            D3D.Resize(width, height);
        }
        
        public void BeginRender(Vector4 clearColor)
        {
            // Clear the buffer to begin the scene.
            D3D.BeginScene(clearColor.X, clearColor.Y, clearColor.Z, clearColor.Z);

            // render camera and set values to its components 
            mPrimitivesRenderer.SetTransformMatrices(WorldMatrix, mCamera.ViewMatrix, mCamera.ProjectionMatrix);
        }

        public void EndRender()
        {
            // Present the rendered scene to the screen.
            D3D.EndScene();
        }

        /// <summary>
        /// Renders a point
        /// </summary>
        public void RenderPoint(Vector3 point, Vector4 color)
        {
            mPrimitivesRenderer.RenderPoint(point, color);
        }

        /// <summary>
        /// Renders a line
        /// </summary>
        public void RenderLine(Vector3 p0, Vector3 p1, Vector4 color)
        {
            // render camera and set values to its components 
            mPrimitivesRenderer.SetTransformMatrices(WorldMatrix, mCamera.ViewMatrix, mCamera.ProjectionMatrix);

            mPrimitivesRenderer.RenderLine(p0, p1, color);
        }

        /// <summary>
        /// Sets the camera settings
        /// </summary>
        public void SetCamera(Vector3 position, Vector3 lookAt, Vector3 up)
        {
            mCamera.SetLookAt(position, lookAt, up);
        }

        /// <summary>
        /// Sets the FOV of camera
        /// </summary>
        public void SetFOV(float fov)
        {
            mCamera.SetFOV(fov);
        }
        /// <summary>
        /// Sets the near and far limits of the camera
        /// </summary>
        public void SetNearFarLimits(float near, float far)
        {
            mCamera.SetNearFarLimits(near, far);
        }


        #endregion

        /// <summary>
        /// Updates the perspective matrix
        /// </summary>
        internal void UpdatePerspective(int windowWidth, int windowHeight)
        {
            mCamera.UpdatePerspective(windowWidth, windowHeight);
        }

        /// <summary>
        /// Enables the blend state
        /// </summary>
        public void EnableBlendState()
        {
            D3D.EnableBlendState();
        }
        
        /// <summary>
        /// Disables the blend state
        /// </summary>
        public void DisableBlendState()
        {
            D3D.DisableBlendState();
        }


        ///// <summary>
        ///// TO-DO: Refactor to BeginRender a EndRender methods + request event to re-render the stuff
        ///// </summary>
        ///// <param name="rotation"></param>
        ///// <returns></returns>
        //private bool Render(float rotation)
        //{

        //    // Generate the view matrix based on the camera position.
        //    //Camera.Render();

        //    //// Get the world, view, and projection matrices from camera and d3d objects.
        //    //var viewMatrix = Camera.ViewMatrix;
        //    //var worldMatrix = D3D.WorldMatrix;
        //    //var projectionMatrix = D3D.ProjectionMatrix;

        //    // Rotate the world matrix by the rotation value so that the triangle will spin.
        //    //Matrix.RotationY(rotation, out worldMatrix);

        //    // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
        //    //Model.Render(D3D.DeviceContext);

        //    // Render the model using the color shader.
        //    //if (!LightShader.Render(D3D.DeviceContext, Model.IndexCount, worldMatrix, viewMatrix, projectionMatrix, Model.Texture.TextureResource, Light.Direction, Light.AmbientColor, Light.DiffuseColor, Camera.GetPosition(), Light.SpecularColor, Light.SpecularPower))
        //    //    return false;


        //    return true;
        //}
        #endregion

        #region Static Methods
        static void Rotate()
        {
            Rotation += (float)Math.PI * 0.005f;
            if (Rotation > 360)
                Rotation -= 360;
        }
        #endregion

        #region Override Methods
        public object Clone()
        {
            return MemberwiseClone();
        }
        #endregion
    }
}
